package top.itaso.lib.common;

import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.util.Log;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Description: 文件相关便捷操作，其中所有 API 均会提供 File 和 FilePath 类型的参数
 * @Author itsdf07
 * @E-Mail 923255742@qq.com
 * @Github https://github.com/itsdf07
 * @Date 2021/11/22
 */
public class FileUtils {
    private static final String TAG = FileUtils.class.getSimpleName();
    /**
     * 单线程池，主要用于避免文件操作时，多线程出现对同一个文件进行操作
     */
    private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();

    public interface IFileComplateCallback {
        /**
         * 文件事件执行结束回调，如：<br>
         * 单/批量 文件进行 Del/Copy 等动作完成时执行，可用于通知 UI 交互
         *
         * @param sucFiles    成功的文件
         * @param failedFiles 失败的文件
         */
        void onComplate(List<File> sucFiles, List<File> failedFiles);
    }

    public interface IFileReplaceListener {
        /**
         * 同名文件是否覆盖
         * 注意，这个源文件是目标文件，比如从 U盘(/storage/sda1/) 根目录把 update.zip 文件拷贝到 /data/ota_package/ 目录下，那么
         * sourceFile：/storage/sda1/update.zip
         * destFile：/data/ota_package/update.zip
         *
         * @param sourceFile 等待操作的源文件
         * @param destFile   操作目标
         * @return {@code true}: 覆盖（先删除源文件）<br>{@code false}: 忽略（不替换）
         */
        boolean isReplace(File sourceFile, File destFile);
    }

    /**
     * 根据 dir 获取 【文件】
     *
     * @param dir 完整路径。如 /storage/emulated/0/Download/test.txt
     * @return {@code File}：File(路径不合法时，返回 null)
     */
    public static File getFileByDir(String dir) {
        return TextUtils.isEmpty(dir) ? null : new File(dir);
    }

    /**
     * 判断 dir 是否为【文件夹】
     *
     * @param dir 完整路径。如 /storage/emulated/0/Download
     * @return {@code true}: 文件夹<br>{@code false}: 不是文件夹
     */
    public static boolean isFolder(String dir) {
        File file = getFileByDir(dir);
        return isFolder(file);
    }

    /**
     * 判断 folder 是否为【文件夹】
     *
     * @param folder
     * @return {@code true}: 文件夹<br>{@code false}: 不是文件夹
     */
    public static boolean isFolder(File folder) {
        return isFileExists(folder) && folder.isDirectory();
    }

    /**
     * 判断 dir 是否为【文件】
     *
     * @param dir
     * @return {@code true}: 文件<br>{@code false}: 不是文件
     */
    public static boolean isFile(String dir) {
        File file = getFileByDir(dir);
        return isFile(file);
    }

    /**
     * 判断 file 是否为【文件】
     *
     * @param file
     * @return {@code true}: 文件<br>{@code false}: 不是文件
     */
    public static boolean isFile(File file) {
        return isFileExists(file) && file.isFile();
    }

    /**
     * 判断 dir 【文件/文件夹】是否存在
     *
     * @param dir
     * @return {@code true}: 是<br>{@code false}: 否
     */
    public static boolean isFileExists(String dir) {
        File file = getFileByDir(dir);
        return isFileExists(file);
    }

    /**
     * 判断 dir 【文件/文件夹】是否存在
     *
     * @param file
     * @return {@code true}: 是<br>{@code false}: 否
     */
    private static boolean isFileExists(File file) {
        return null != file && file.exists();
    }

    /**
     * 判断指定 dir 下是否有子文件：会判断是否为文件夹，在判断是否有子文件
     *
     * @param dir 完整路径。如 /storage/emulated/0/Download 或者 /storage/emulated/0/Download/test.txt
     * @return {@code true}: 有<br>{@code false}: 没有
     */
    public static boolean hasChileFiles(String dir) {
        File file = getFileByDir(dir);
        return hasChileFiles(file);
    }


    /**
     * 判断指定 folder 下是否有子文件
     *
     * @param folder
     * @return {@code true}: 有<br>{@code false}: 没有
     */
    private static boolean hasChileFiles(File folder) {
        if (!isFolder(folder)) {
            //路径不存在、不是目录
            return false;
        }
        File files[] = folder.listFiles();
        if (null != files && files.length > 0) {
            return true;
        }
        return false;
    }

    /**
     * 获取 dir 的所有 子文件/文件夹 集，不包含递归文件夹下的子文件，使用时需判 null
     *
     * @param dir
     * @return {@code 文件列表}
     */
    public static File[] getFileList(String dir) {
        if (!isFolder(dir)) {
            return new File[0];
        }
        File file = getFileByDir(dir);
        if (file == null) {
            return new File[0];
        }
        File[] fileLists = file.listFiles();
        Log.d(TAG, "getFileList: >>>>>>dir=" + dir + ",fileLists=" + Arrays.toString(fileLists));
        return fileLists;
    }


    /**
     * 根据文件路径
     * 返回文件修改时间
     *
     * @param dir 文件路径
     * @return {@code 返回文件上次修改的时间}
     */
    public static long getLastModified(String dir) {
        File file = getFileByDir(dir);
        return file == null ? 0 : file.lastModified();
    }

    //------------------------------------------------------------------------------

    /**
     * 创建【目录】：如果父级目录不存在，则一同创建<br/>
     * 1、如果目标 路径 已存在，则返回是否为目录<br/>
     * 2、如果目标 路径 不存在，则返回创建目录是否成功<br/>
     *
     * @param dir
     * @return {@code true}: 存在且是目录/目录创建成功<br>{@code false}: 创建失败
     */
    public static boolean createOrExistsFolder(String dir) {
        File folder = getFileByDir(dir);
        return createOrExistsFolder(folder);
    }

    /**
     * 创建【目录】：如果父级目录不存在，则一同创建<br/>
     * 1、如果目标 folder 已存在，则返回是否为目录<br/>
     * 2、如果目标 folder 不存在，则返回创建目录是否成功<br/>
     *
     * @param folder
     * @return {@code true}: 存在且是目录/目录创建成功<br>{@code false}: 创建失败
     */
    private static boolean createOrExistsFolder(File folder) {
        if (null == folder) {
            return false;
        }
        if (folder.exists()) {
            return folder.isDirectory();
        }
        return folder.mkdirs();
//        return null != file && (file.exists() ? file.isDirectory() : file.mkdirs());
    }

    /**
     * 创建目标【文件】
     * 1、如果目标file已存在，则返回是否为文件<br/>
     * 2、如果file不存在，则优先创建其父目录<br/>
     * 2.1、创建成功，则继续创建文件<br/>
     * 2.2、创建失败，则返回创建文件失败<br/>
     * 2、如果目标file不存在，则返回创建目标目录是否成功<br/>
     *
     * @param dir 文件路径
     * @return {@code true}: 存在或创建成功<br>{@code false}: 不存在或创建失败
     */
    public static boolean createOrExistsFile(String dir) {
        File file = getFileByDir(dir);
        return createOrExistsFile(file);
    }

    /**
     * 创建目标【文件】
     * 1、如果目标file已存在，则返回是否为文件<br/>
     * 2、如果file不存在，则优先创建其父目录<br/>
     * 2.1、创建成功，则继续创建文件<br/>
     * 2.2、创建失败，则返回创建文件失败<br/>
     * 2、如果目标file不存在，则返回创建目标目录是否成功<br/>
     *
     * @param file 文件
     * @return {@code true}: 存在或创建成功<br>{@code false}: 不存在或创建失败
     */
    private static boolean createOrExistsFile(File file) {
        if (null == file) {
            return false;
        }
        if (file.exists()) {
            return file.isFile();
        }

        if (!createOrExistsFolder(file.getParentFile())) {//创建文件目录
            return false;
        }
        try {
            return file.createNewFile();//创建文件
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    //------------------------------------------------------------------------------

    /**
     * 文件/文件夹 拷贝，强制开辟线程进行拷贝
     *
     * @param sourceFiles 需要拷贝的文件，可以是 文件夹/文件
     * @param destFolder  目标目录
     * @param callback    文件拷贝完成回调
     * @param listener    同名文件覆盖监听，listener=null 或者 listener 返回true 时，则覆盖目标文件（先删除在复制过去）
     */
    public static void copyFiles(ArrayList<File> sourceFiles,
                                 File destFolder,
                                 IFileComplateCallback callback,
                                 IFileReplaceListener listener) {
        if (sourceFiles == null || sourceFiles.isEmpty()) {
            if (callback != null) {
                callback.onComplate(new ArrayList<>(), new ArrayList<>());
            }
            return;
        }

        //线程安全的无界非阻塞队列
        ConcurrentLinkedQueue<File> sucFiles = new ConcurrentLinkedQueue<>();
        ConcurrentLinkedQueue<File> failedFiles = new ConcurrentLinkedQueue<>();

        AtomicInteger taskCount = new AtomicInteger(sourceFiles.size());

        for (final File sourceFile : sourceFiles) {
            //使用单线程池，避免多个线程同时操作一个文件
            EXECUTOR_SERVICE.submit(() -> {
                Log.v(TAG, "copyFiles: >>>>>>sourceFile=" + sourceFile.getAbsolutePath());
                Log.v(TAG, "copyFiles: >>>>>>destFolder=" + destFolder.getAbsolutePath());
                try {
                    if (sourceFile.isDirectory()) {
                        copyDirectory(sourceFile, new File(destFolder, sourceFile.getName()), sucFiles, failedFiles, listener);
                    } else {
                        copyFile(sourceFile, destFolder, sucFiles, failedFiles, listener);
                    }
                } finally {
                    // 所有任务完成后回调
                    if (taskCount.decrementAndGet() == 0 && callback != null) {
                        //把 ConcurrentLinkedQueue 转成 ArrayList
                        callback.onComplate(new ArrayList<>(failedFiles), new ArrayList<>(failedFiles));
                    }
                }
            });
        }
    }


    /**
     * @param sourceFolder 需要拷贝的目录
     * @param destFolder   拷贝的目标目录
     * @param sucFiles
     * @param failedFiles
     * @param listener
     * @return
     */
    private static boolean copyDirectory(File sourceFolder,
                                         File destFolder,
                                         ConcurrentLinkedQueue<File> sucFiles,
                                         ConcurrentLinkedQueue<File> failedFiles,
                                         IFileReplaceListener listener) {

        if (!createOrExistsFolder(destFolder)) {//目标目录不存在或者创建失败
            Log.d(TAG, "copyDirectory: >>>>>>目标目录创建失败");
            failedFiles.add(sourceFolder);
            return false;
        }
        if (isFile(sourceFolder)) {
            Log.w(TAG, "copyDirectory: >>>>>>" + sourceFolder.getAbsolutePath() + "是一个文件");
            copyFile(sourceFolder, destFolder, sucFiles, failedFiles, listener);
            return true;
        }
        File[] sourceFiles = sourceFolder.listFiles();

        if (sourceFiles == null || sourceFiles.length == 0) {//目标目录不存在或者创建失败
            Log.d(TAG, "copyDirectory: >>>>>>" + sourceFolder.getAbsolutePath() + "目录下没有文件");
            failedFiles.add(sourceFolder);
            return false;
        }

        for (File sourceFile : sourceFiles) {
            Log.v(TAG, "copyFiles: >>>>>>sourceFile=" + sourceFile.getAbsolutePath());
            Log.v(TAG, "copyFiles: >>>>>>destFolder=" + destFolder.getAbsolutePath());
            if (sourceFile.isDirectory()) {
                copyDirectory(sourceFile, new File(destFolder, sourceFile.getName()), sucFiles, failedFiles, listener);
            } else {
                copyFile(sourceFile, destFolder, sucFiles, failedFiles, listener);
            }
        }
        return true;
    }

    /**
     * @param sourceFile  拷贝对象，如果是文件夹，sourceFile 为 文件夹 File，如果是个文件，则 sourceFile 是文件的全路径
     * @param destFolder  拷贝目标的目录
     * @param sucFiles
     * @param failedFiles
     * @param listener
     */
    private static void copyFile(File sourceFile,
                                 File destFolder,
                                 ConcurrentLinkedQueue<File> sucFiles,
                                 ConcurrentLinkedQueue<File> failedFiles,
                                 IFileReplaceListener listener) {
        Log.d(TAG, "copyFile: >>>>>>sourceFile=" + sourceFile.getAbsolutePath() + ",destFolder=" + destFolder.getAbsolutePath());
        if (!isFileExists(sourceFile)) {//被拷贝文件不存在
            failedFiles.add(sourceFile);
            return;
        }
        if (!createOrExistsFolder(destFolder)) {//目标目录不存在或者创建失败
            failedFiles.add(sourceFile);
            return;
        }
        //这边的 sourceFile 一定不会为 null
        if (sourceFile.getParent().equals(destFolder)) {//目标文件位置无变化
            sucFiles.add(sourceFile);
            return;
        }

        //默认（不关心 listener，即 listener==null  或者明确需要覆盖源文件时）
        File destFile = new File(destFolder.getAbsolutePath(), sourceFile.getName());
        if (listener == null || listener.isReplace(sourceFile, destFile)) {//
            if (isFileExists(destFile) && !destFile.delete()) {// unsuccessfully delete then return
                //如果删除失败，则 copy 失败，
                failedFiles.add(sourceFile);
                return;
            } else {
                //TODO 往下继续执行copy动作，所以这边不能return
            }
        } else {
            sucFiles.add(sourceFile);
            return;
        }

        //TODO 执行 File 的 copy 动作
//        boolean isCopySuccess = copyFile(sourceFilePath, destFilePath);
        if (performCopyFile(sourceFile, destFile)) {
            sucFiles.add(sourceFile);
        } else {
            failedFiles.add(sourceFile);
        }
    }

    /**
     * 复制单个文件：已确保 sourceFile 和 destFile 的合法性
     *
     * @param sourceFile String 原文件路径+文件名 如：data/user/0/com.test/files/abc.txt
     * @param destFile   String 复制后路径+文件名 如：data/user/0/com.test/cache/abc.txt
     * @return true-复制成功，false-复制失败
     */
    private static boolean performCopyFile(File sourceFile, File destFile) {
        Log.d(TAG, "performCopyFile: >>>>>>" + sourceFile.getAbsolutePath() + ">>>" + destFile.getAbsolutePath());
        //使用 try-with-resources 确保资源自动关闭
        try (FileInputStream fileInputStream = new FileInputStream(sourceFile);
             FileOutputStream fileOutputStream = new FileOutputStream(destFile)) {

            byte[] buffer = new byte[1024 * 2];
            int byteRead;
            while (-1 != (byteRead = fileInputStream.read(buffer))) {
                fileOutputStream.write(buffer, 0, byteRead);
            }
//            fileOutputStream.flush();
        } catch (FileNotFoundException e) {
            Log.e(TAG, "performCopyFile: >>>>>>文件未找到", e);
        } catch (IOException e) {
            Log.e(TAG, "performCopyFile: >>>>>>IO异常", e);
        } catch (Exception e) {
            Log.e(TAG, "performCopyFile: >>>>>>未知异常", e);
        }
        return false;// 出现任何异常都应返回 false
    }

    //------------------------------------------------------------------------------
    /**
     * ----------------------------------------------------
     *
     */

    /**
     * 批量删除指定路径下的所有文件及子文件
     *
     * @param file       需要删除的垃圾文件(单个File)
     * @param needRename 是否需要重命名的方式来删除
     */
    public static void deleteFile(final File file, final boolean needRename, final boolean inThread, IFileComplateCallback callback) {
        if (!isFileExists(file)) {
            if (callback != null) {
                ArrayList<File> failureFiles = new ArrayList<>();
                if (null != file) {
                    failureFiles.add(file);
                }
                callback.onComplate(new ArrayList<>(), failureFiles);
            }
            return;
        }
        deleteFilePath(file.getAbsolutePath(), needRename, inThread, callback);
    }

    /**
     * 批量删除指定文件
     *
     * @param files      需要删除的垃圾文件(单个File)
     * @param needRename 是否需要重命名的方式来删除
     */
    public static void deleteFiles(final ArrayList<File> files, final boolean needRename, final boolean inThread, IFileComplateCallback callback) {
        if (files == null) {
            if (callback != null) {
                callback.onComplate(new ArrayList<>(), new ArrayList<>());
            }
            return;
        }

        ArrayList<String> filePaths = new ArrayList<>();
        for (File file : files) {
            filePaths.add(file.getAbsolutePath());
        }
        deleteFilePaths(filePaths, needRename, inThread, callback);
    }

    /**
     * 批量删除指定路径下的所有文件及子文件
     *
     * @param path       需要删除的垃圾文件(单个File)
     * @param needRename 是否需要重命名的方式来删除
     */
    public static void deleteFilePath(final String path, final boolean needRename, final boolean inThread, IFileComplateCallback callback) {
        if (!isFileExists(path)) {
            if (callback != null) {
                ArrayList<File> failureFiles = new ArrayList<>();
                if (!TextUtils.isEmpty(path)) {
                    failureFiles.add(new File(path));
                }
                callback.onComplate(new ArrayList<>(), failureFiles);
            }
            return;
        }
        ArrayList<String> filePaths = new ArrayList<>();
        filePaths.add(path);
        deleteFilePaths(filePaths, needRename, inThread, callback);
    }

    /**
     * 批量删除指定文件
     *
     * @param filePaths  需要删除的垃圾文件(单个File)
     * @param needRename 是否需要重命名的方式来删除
     */
    public static void deleteFilePaths(final ArrayList<String> filePaths, final boolean needRename, final boolean inThread, IFileComplateCallback callback) {
        if (filePaths == null) {
            if (callback != null) {
                callback.onComplate(new ArrayList<>(), new ArrayList<>());
            }
            return;
        }
        doDeleteFiles(filePaths, needRename, inThread, callback);
    }


    /**
     * 在线程中删除【文件】
     *
     * @param filePaths  需要删除的垃圾文件(单个File)
     * @param needRename 是否需要重命名的方式来删除
     */
    private static void doDeleteFiles(final ArrayList<String> filePaths, final boolean needRename, final boolean inThread, final IFileComplateCallback callback) {
        final ArrayList<File> sucFiles = new ArrayList<>();
        final ArrayList<File> failureFiles = new ArrayList<>();
        if (inThread) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (String filePath : filePaths) {
                        realDeleteFilesAndChildFiles(filePath, needRename, sucFiles, failureFiles);
                    }
                    if (callback != null) {
                        callback.onComplate(sucFiles, failureFiles);
                    }
                }
            }).start();
        } else {
            for (String filePath : filePaths) {
                realDeleteFilesAndChildFiles(filePath, needRename, sucFiles, failureFiles);
            }
        }
    }

    /**
     * 删除指定路径下的文件及文件夹
     *
     * @param filePath 指定文件夹或者文件路径
     */
    private static void realDeleteFilesAndChildFiles(String filePath, boolean needRename, ArrayList<File> sucFiles, ArrayList<File> failureFiles) {
        if (!TextUtils.isEmpty(filePath) && isFileExists(filePath)) {
            try {
                Log.v(TAG, "realDeleteFilesAndChildFiles: >>>>>>will delete filePath=" + filePath + ",and will " + (needRename ? "rename" : "do not rename"));
                File file = new File(filePath);
                if (needRename) {
                    File fileRename = new File(filePath + System.currentTimeMillis());
                    if (file.renameTo(fileRename)) {
                        file = fileRename;
                    }
                    Log.v(TAG, "realDeleteFilesAndChildFiles: >>>>>>renamed filePaht=" + file.getAbsolutePath());
                }
                if (file.isDirectory()) {//如果指定的绝对路径是一个目录，即文件夹，那么取出该目录下的列表
                    File files[] = file.listFiles();
                    for (int i = 0; i < files.length; i++) {
                        String childPath = files[i].getAbsolutePath();
                        if (!TextUtils.isEmpty(childPath) && isFileExists(childPath)) {
                            realDeleteFilesAndChildFiles(childPath, needRename, sucFiles, failureFiles);
                        }
                    }
                }
                if (file.delete()) {
                    sucFiles.add(new File(filePath));
                } else {
                    failureFiles.add(new File(filePath));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            failureFiles.add(new File(filePath));
        }
    }


    /**
     * 重命名【文件】
     *
     * @param path    文件路径
     * @param newName 新名称
     * @return {@code true}: 重命名成功<br>{@code false}: 重命名失败
     */
    public static boolean rename(String path, String newName) {
        File file = getFileByDir(path);
        return rename(file, newName);
    }

    /**
     * 重命名文件
     *
     * @param file    文件
     * @param newName 新名称
     * @return {@code true}: 重命名成功<br>{@code false}: 重命名失败
     */
    public static boolean rename(File file, String newName) {
        if (!isFileExists(file)) {
            return false;
        }

        // 新的文件名为空返回false
        if (TextUtils.isEmpty(newName)) {
            return false;
        }
        // 如果文件名没有改变返回true
        if (newName.equals(file.getName())) {
            return true;
        }
        // 检查父目录是否存在
        String parent = file.getParent();
        if (parent == null) {
            return false;
        }

        File newFile = new File(file.getParent(), newName);
        // 如果重命名的文件已存在返回false
        return !newFile.exists() && file.renameTo(newFile);
    }

    /**
     * ----------------------------------------------------
     */

    public static final int SIZETYPE_B = 1;// 获取文件大小单位为B的double值
    public static final int SIZETYPE_KB = 2;// 获取文件大小单位为KB的double值
    public static final int SIZETYPE_MB = 3;// 获取文件大小单位为MB的double值
    public static final int SIZETYPE_GB = 4;// 获取文件大小单位为GB的double值
    public static final int FILESCALE_1024 = 1024;//文件大小单位换算比例

    /**
     * 调用此方法计算指定文件或指定文件夹的大小
     *
     * @param filePath 文件路径
     * @param sizeType {@code B}: 1<br>{@code KB}: 2<br>{@code MB}: 3<br>{@code GB}: 4
     * @return double值的大小
     */
    public static double getFileOrFilesSize(String filePath, int sizeType) {
        File file = getFileByDir(filePath);
        long blockSize = 0;
        if (null == file) {
            return blockSize;
        }
        try {
            if (file.isDirectory()) {
                blockSize = getFileSizes(file);
            } else {
                blockSize = getFileSize(file);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return formatFileSize(blockSize, sizeType);
    }

    /**
     * 调用此方法自动计算指定文件或指定文件夹的大小
     *
     * @param filePath 文件路径
     * @return 计算好的带B、KB、MB、GB的字符串
     */
    public static String getAutoFileOrFilesSize(String filePath) {
        File file = getFileByDir(filePath);
        long blockSize = 0;
        if (null == file) {
            return blockSize + "";
        }
        try {
            if (file.isDirectory()) {
                blockSize = getFileSizes(file);
            } else {
                blockSize = getFileSize(file);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return formatFileSize(blockSize);
    }

    /**
     * 获取指定文件大小
     *
     * @param file
     * @return
     * @throws Exception
     */
    private static long getFileSize(File file) throws Exception {
        long size = 0;
        if (file == null) {
            return size;
        }
        if (file.exists()) {
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(file);
                size = fis.available();
            } catch (IOException e) {

            } finally {
                fis.close();
            }
        } else {
            file.createNewFile();
        }
        return size;
    }

    /**
     * 获取指定文件夹大小
     *
     * @param file
     * @return
     * @throws Exception
     */
    private static long getFileSizes(File file) throws Exception {
        long size = 0;
        if (file == null) {
            return size;
        }
        File flist[] = file.listFiles();
        for (int i = 0; i < flist.length; i++) {
            if (flist[i].isDirectory()) {
                size = size + getFileSizes(flist[i]);
            } else {
                size = size + getFileSize(flist[i]);
            }
        }
        return size;
    }

    /**
     * 转换文件大小
     *
     * @param fileS
     * @return
     */
    public static String formatFileSize(long fileS) {
        DecimalFormat df = new DecimalFormat("#.00");
        String fileSizeString = "";
        String wrongSize = "0B";
        if (fileS == 0) {
            return wrongSize;
        }
        if (fileS < FILESCALE_1024) {
            fileSizeString = df.format((double) fileS) + "B";
        } else if (fileS < (FILESCALE_1024 * FILESCALE_1024)) {
            fileSizeString = df.format((double) fileS / FILESCALE_1024) + "KB";
        } else if (fileS < (FILESCALE_1024 * FILESCALE_1024 * FILESCALE_1024)) {
            fileSizeString = df.format((double) fileS / (FILESCALE_1024 * FILESCALE_1024)) + "MB";
        } else {
            fileSizeString = df.format((double) fileS / (FILESCALE_1024 * FILESCALE_1024 * FILESCALE_1024)) + "GB";
        }
        return fileSizeString;
    }

    /**
     * 转换文件大小,指定转换的类型
     *
     * @param fileS
     * @param sizeType
     * @return
     */
    public static double formatFileSize(long fileS, int sizeType) {
        DecimalFormat df = new DecimalFormat("#.00");
        double fileSizeLong = 0;
        switch (sizeType) {
            case SIZETYPE_B:
                fileSizeLong = Double.valueOf(df.format((double) fileS));
                break;
            case SIZETYPE_KB:
                fileSizeLong = Double.valueOf(df.format((double) fileS / FILESCALE_1024));
                break;
            case SIZETYPE_MB:
                fileSizeLong = Double.valueOf(df.format((double) fileS / (FILESCALE_1024 * FILESCALE_1024)));
                break;
            case SIZETYPE_GB:
                fileSizeLong = Double.valueOf(df
                        .format((double) fileS / (FILESCALE_1024 * FILESCALE_1024 * FILESCALE_1024)));
                break;
            default:
                break;
        }
        return fileSizeLong;
    }

    /**
     * 外部存储是否可用 (存在且具有读写权限)
     *
     * @return
     */

    public static boolean isExternalStorageAvailable() {
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);// MEDIA_MOUNTED_READ_ONLY (SDcard存在，只可以进行读操作)
    }

    /**
     * 获取剩余可以空间总大小(不包括保留块部分)
     *
     * @return 文件系统的可用大小
     */

    public static long getAvailableSpace(String path) {
        StatFs stat = new StatFs(path);
        long availableSize;//剩余可用空间总大小
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
            long blockSize = stat.getBlockSize();
            long availableBlocks = stat.getAvailableBlocks();
            availableSize = blockSize * availableBlocks;
        } else {
            availableSize = stat.getAvailableBytes();
        }
        return availableSize;
    }

    /**
     * 获取可以空间总大小(不包括保留块部分)
     *
     * @return 文件系统的总大小
     */

    public static long getTotalSpace(String path) {
        StatFs stat = new StatFs(path);
        long totalSize;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
            long blockSize = stat.getBlockSize();
            long totalBlocks = stat.getBlockCount();
            totalSize = blockSize * totalBlocks;
        } else {
            totalSize = stat.getTotalBytes();
        }

        return totalSize;
    }

    /**
     * 输出存储空间相关数据
     *
     * @param path
     * @return
     */
    public static String queryStorageSpace(String path) {
        StatFs stat = new StatFs(path);

        long blockSize;      //每个block 占字节数
        long totalBlocks;   //存储块总数量
        long availableCount;//可用存储块数
        long freeBlocks;//剩余块数量，注：这个包含保留块（including reserved blocks）即应用无法使用的空间
        long totalSize;//可用空间总大小
        long freeTotalSize;//文件系统上可用的块总数
        long availableSize;//剩余可用空间大小
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
            blockSize = stat.getBlockSize();
            totalBlocks = stat.getBlockCount();
            availableCount = stat.getAvailableBlocks();
            freeBlocks = stat.getFreeBlocks();
            freeTotalSize = blockSize * freeBlocks;

            totalSize = blockSize * totalBlocks;
            availableSize = blockSize * availableCount;
        } else {
            blockSize = stat.getBlockSizeLong();
            totalBlocks = stat.getBlockCountLong();
            availableCount = stat.getAvailableBlocksLong();
            freeBlocks = stat.getFreeBlocksLong();

            //API level 18（JELLY_BEAN_MR2）引入
            totalSize = stat.getTotalBytes();
            availableSize = stat.getAvailableBytes();
            freeTotalSize = stat.getFreeBytes();
        }
        StringBuilder storageSpaceInfo = new StringBuilder();

        storageSpaceInfo.append(path + " 存储信息：\n");
        storageSpaceInfo.append("单个存储块大小:" + formatFileSize(blockSize) + "\n");
        storageSpaceInfo.append("存储块总数:" + totalBlocks + "\n");
        storageSpaceInfo.append("可用存储块数:" + availableCount + "\n");
        storageSpaceInfo.append("可用存储块数(含保留块):" + freeBlocks + "\n");
        storageSpaceInfo.append("可用空间总大小:" + formatFileSize(totalSize) + "\n");
        storageSpaceInfo.append("剩余可用空间总大小(含保留块):" + formatFileSize(freeTotalSize) + "\n");
        storageSpaceInfo.append("剩余可用空间总大小:" + formatFileSize(availableSize) + "\n");
        return storageSpaceInfo.toString();
    }

    /**
     * ----------------------------------------------------
     */

    /**
     * 获取内置sd卡路径，<br/>
     * 目前个人认为可以移除就是外卡，不能移除就是内卡，只经过大量试验，没有任何依据！此处需慎重！！！
     *
     * @param context
     * @return 内卡路径
     */
    public static String getInnerSDPath(Context context) {
        int currentapiVersion = Build.VERSION.SDK_INT;
        if (currentapiVersion >= 12) {
            try {
                StorageManager manager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
                /************** StorageManager的方法 ***********************/
                Method getVolumeList = StorageManager.class.getMethod("getVolumeList");
                Method getVolumeState = StorageManager.class.getMethod("getVolumeState", String.class);

                Object[] Volumes = (Object[]) getVolumeList.invoke(manager);
                String state = null;
                String path = null;

                for (Object volume : Volumes) {
                    /************** StorageVolume的方法 ***********************/
                    Method getPath = volume.getClass().getMethod("getPath");
                    path = (String) getPath.invoke(volume);
                    state = (String) getVolumeState.invoke(manager, getPath.invoke(volume));

                    /**
                     * 是否可以移除(内置sdcard) TODO:
                     * 目前个人认为可以移除就是外卡，不能移除就是内卡，只经过大量试验，没有任何依据！此处需慎重！！！
                     */
                    Method isRemovable = volume.getClass().getMethod("isRemovable");
                    boolean removable = (Boolean) isRemovable.invoke(volume);

                    if (null != path && null != state && state.equals(Environment.MEDIA_MOUNTED)) {
                        if (false == removable) {
                            return path;
                        }
                    }
                }
            } catch (Exception e) {
                return "";
            }
        }
        // 得到存储卡路径
        File sdDir = null;
        boolean sdCardExist = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); // 判断sd卡
        // 或可存储空间是否存在
        if (sdCardExist) {
            sdDir = Environment.getExternalStorageDirectory();// 获取sd卡或可存储空间的跟目录
            return sdDir.toString();
        }

        return "";
    }

    /**
     * 信息写入文件
     *
     * @param file    写入的文件
     * @param content 写入的数据
     * @param append  是否覆盖写入
     * @return {@code true}: 信息写入成功<br>{@code false}: 信息写入失败
     */
    public static boolean write2File(File file, String content, boolean append) {
        if (TextUtils.isEmpty(content)) {
            content = "";
        }
        byte[] aData = content.getBytes();
        return write2File(file, aData, append);
    }

    /**
     * 信息写入文件
     *
     * @param file   写入的文件
     * @param aData  写入的数据
     * @param append 是否覆盖写入
     * @return {@code true}: 信息写入成功<br>{@code false}: 信息写入失败
     */
    public static boolean write2File(File file, byte[] aData, boolean append) {
        if (!createOrExistsFile(file)) {
            //写入文件不存在且创建失败时return false
            return false;
        }
        OutputStream out = null;
        boolean ok = false;
        try {
            out = new FileOutputStream(file, append);
            out.write(aData);
            ok = true;
        } catch (Exception e) {
            Log.e(TAG, "log write failure:" + e.getMessage());
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    Log.e(TAG, "OutputStream close failure:" + e.getMessage());
                }
            }
        }
        return ok;
    }


    public static void readLineFromFile(String path) {
        Log.d(TAG, "readLineFromFile: >>>>>>path=" + path);
        if (isFolder(path)) {
            return;
        }
        Log.d(TAG, "readLineFromFile: >>>>>>1");
//        if (!isFile(path)) {
//            return;
//        }
        Log.d(TAG, "readLineFromFile: >>>>>>2");
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 建立从文件到程序的数据输入(input)流
                FileInputStream fis = null;
                InputStreamReader isr = null;
                BufferedReader reader = null;
                try {
                    fis = new FileInputStream(path);
                    // 用 InputStreamReader 包装 byte流，并指定字符编码，让它能够将读出的byte转为字符
                    isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
                    //  增加缓冲功能，输入输出效率更高，并且可以一次读一行
                    reader = new BufferedReader(isr);
                    String line;//= reader.readLine();
//            Log.d(TAG, "readLineFromFile: >>>>>>" + line);
                    File newFile = new File("/storage/emulated/0/new_abcd.docx");
                    while ((line = reader.readLine()) != null) {
                        line.replace("苏沐宸", "苏登福");
                        Log.d(TAG, "readLineFromFile: >>>>>>::" + line);
                        write2File(newFile, line, true);
                        Thread.sleep(2000);
                    }
                } catch (IOException | InterruptedException e) {
                    Log.e(TAG, "readLineFromFile: >>>>>>e=", e);
                    e.printStackTrace();
                }
            }
        }).start();


    }
}
